#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <assert.h>

#define LEFT        "["     // 左边界符号
#define RIGHT       "]"     // 右边界符号
#define DELIM       " \t"   // 分隔符：空格和制表符
#define LINE_SIZE   1024    // 命令行输入的最大长度
#define ARGC_SIZE   32      // 命令行参数的最大数量

char commandline[LINE_SIZE]; // 存储命令行输入
char pwd[LINE_SIZE];         // 存储当前工作目录
char* argv[ARGC_SIZE];       // 存储命令行参数
int lastcode = 0;            // 上一个命令的退出码
int quit = 0;                // 是否退出Shell的标志
char myenv[LINE_SIZE];       // 存储export命令设置的环境变量

enum EXIT {
    GETHOSTNAME_FAILED = 1,
    FORK_FAILED = 2,
    EXIT_CODE = 3,
};

// 获取当前工作目录
void GetPwd() {
    getcwd(pwd, sizeof(pwd));
}

// 获取当前用户名
const char* GetUserName() {
    return getenv("USER");
}

// 获取主机名
const char* GetHostName() {
    static int initialized = 0;
    if (!initialized) {
        // 设置HOSTNAME为"remote"
        if (putenv("HOSTNAME=remote") != 0) {
            perror("HOSTNAME获取失败");
            exit(GETHOSTNAME_FAILED);
        }
        initialized = 1;
    }
    return getenv("HOSTNAME");
}

// 交互函数，获取用户输入的命令行
void interact(char* cline, int size) {
    GetPwd();
    // 打印Shell提示符：[用户名@主机名当前工作目录]
    printf(LEFT "%s@%s%s" RIGHT "\n", GetUserName(), GetHostName(), pwd);
    // 从标准输入读取用户输入的命令行
    char* s = fgets(cline, size, stdin);
    if (s == NULL) {
        perror("fgets失败");
        exit(EXIT_CODE);
    }
    // 去掉末尾的换行符
    size_t len = strlen(cline);
    if (cline[len - 1] == '\n') {
        cline[len - 1] = '\0';
    }
}

// 字符串分割函数，将命令行字符串分割为参数数组
int SplitString(char* cline, char* _argv[]) {
    int i = 0;
    _argv[i++] = strtok(cline, DELIM); // 使用strtok第一次分割
    // 使用strtok继续分割，直到达到最大参数数量或分割结束
    while (i < ARGC_SIZE && (_argv[i++] = strtok(NULL, DELIM)));
    return i - 1;
}

// 内建命令处理函数
int BuildCommand(int _argc, char* _argv[]) {
    if (!strcmp(_argv[0], "quit")) {
        quit = 1; // 设置退出标志
    } else if (_argc == 2 && strcmp(_argv[0], "cd") == 0) {
        chdir(_argv[1]); // 改变工作目录
        GetPwd();
        setenv("PWD", pwd, 1); // 更新PWD环境变量
        return 1;
    } else if (_argc == 2 && strcmp(_argv[0], "export") == 0) {
        strcpy(myenv, _argv[1]); // 复制设置的环境变量
        putenv(myenv); // 设置环境变量
        return 1;
    } else if (_argc == 2 && strcmp(_argv[0], "echo") == 0) {
        if (strcmp(_argv[1], "$?") == 0) {
            printf("%d\n", lastcode); // 输出上一个命令的退出码
            lastcode = 0;
        } else if (*_argv[1] == '$') {
            char* val = getenv(_argv[1] + 1);
            if (val) printf("%s\n", val); // 输出环境变量的值
        } else {
            printf("%s\n", _argv[1]); // 输出指定的字符串
        }
        return 1;
    }

    // 特殊处理下的ls命令
    if (strcmp(_argv[0], "ls") == 0) {
        _argv[_argc++] = "--color"; // 增加参数--color
        _argv[_argc] = NULL;
    }
    
    return 0; // 返回0表示不是内建命令
}

// 外部命令执行函数
void NormalExecute(char* _argv[]) {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork失败");
        exit(FORK_FAILED);
    } else if (pid == 0) {
        execvp(_argv[0], _argv); // 在子进程中执行命令
        exit(EXIT_CODE);
    } else {
        int status = 0;
        pid_t rtpid = waitpid(pid, &status, 0); // 等待子进程退出
        if (rtpid == pid) {
            lastcode = WEXITSTATUS(status); // 获取子进程的退出状态
        }
    }
}

int main() {
    while (!quit) {
        interact(commandline, sizeof(commandline)); // 获取用户输入的命令行
        if (strlen(commandline) == 0) continue; // 如果是空行则继续下一轮循环
        int argc = SplitString(commandline, argv); // 分割命令行字符串为参数数组
        if (argc == 0) continue; // 如果没有参数则继续下一轮循环
        
        int is_builtin = BuildCommand(argc, argv); // 执行内建命令
        if (!is_builtin) NormalExecute(argv); // 执行外部命令
    }
    return 0;
}

